/// File:		Camera.cpp
/// Author:		Dennis Norton
/// Date:		04/15/12
/// Purpose:	This class makes up what would be the game's camera.

#include "Camera.h"
#include "../Frame.h"
#include "../Wolf Engine/WEngine.h"
#include "../Wolf Engine/Utilities/Informant.h"
#include "Containers\MeshManager.h"

bool Frustum::IsPointInsideFrustum(const D3DXVECTOR3& Point)
{
	for(int i = 0; i < 6; ++i)
	{
		//float Dot = D3DXPlaneDotCoord(&Planes[i], &Point);
		D3DXVECTOR3 &pNorm = D3DXVECTOR3(Planes[i].a, Planes[i].b, Planes[i].c);
		float Dot = D3DXVec3Dot(&pNorm, &Point) - Planes[i].d;// + 25.0f;

		if(Dot < 0.0f)
			return false;
	}

	return true;
}

bool Frustum::IsRectInsideFrustum(const D3DXVECTOR3& Point, const float& Width, const float& Height)
{
	float HalfWidth = Width * 0.5f;
	float HalfHeight = 0.5f * Height;
	for(int i = 0; i < 6; ++i)
	{
		// Get the plane Normal
		D3DXVECTOR3 &pNorm = D3DXVECTOR3(Planes[i].a, Planes[i].b, Planes[i].c);

		// Calc the sides of the rect
		D3DXVECTOR3 &LeftTop = D3DXVECTOR3(Point.x - HalfWidth, Point.y + HalfHeight, Point.z);
		D3DXVECTOR3 &LeftBottom = D3DXVECTOR3(Point.x - HalfWidth, Point.y - HalfHeight, Point.z);
		D3DXVECTOR3 &RightTop = D3DXVECTOR3(Point.x + HalfWidth, Point.y + HalfHeight, Point.z);
		D3DXVECTOR3 &RightBottom = D3DXVECTOR3(Point.x + HalfWidth, Point.y - HalfHeight, Point.z);
		D3DXVECTOR3 &TopSide = D3DXVECTOR3(Point.x, Point.y + HalfHeight, Point.z);
		D3DXVECTOR3 &BottomSide = D3DXVECTOR3(Point.x, Point.y - HalfHeight, Point.z);


		float Dot = D3DXVec3Dot(&pNorm, &LeftTop) - Planes[i].d + 32.0f;
		if(Dot >= 0.0f) // Left top
			continue;
		Dot = D3DXVec3Dot(&pNorm, &RightTop) - Planes[i].d + 32.0f;
		if(Dot >= 0.0f) // Right top
			continue;
		Dot = D3DXVec3Dot(&pNorm, &LeftBottom) - Planes[i].d + 32.0f;
		if(Dot >= 0.0f) // Left bottom
			continue;
		Dot = D3DXVec3Dot(&pNorm, &RightBottom) - Planes[i].d + 32.0f;
		if(Dot >= 0.0f) // Right bottom
			continue;
		Dot = D3DXVec3Dot(&pNorm, &TopSide) - Planes[i].d + 32.0f;
		if(Dot >= 0.0f) // Top
			continue;
		Dot = D3DXVec3Dot(&pNorm, &BottomSide) - Planes[i].d + 32.0f;
		if(Dot >= 0.0f) // Bottom
			continue;

		return false;
	}

	return true;
}

void Frustum::MakePlane(int index, const D3DXVECTOR3& PointA, const D3DXVECTOR3& PointB, const D3DXVECTOR3& PointC)
{
	D3DXVECTOR3 Normal;
	D3DXVec3Cross(&Normal, &D3DXVECTOR3(PointB - PointA), &D3DXVECTOR3(PointC - PointA));
	D3DXVec3Normalize(&Normal, &-Normal);
	float offset = D3DXVec3Dot(&Normal, &PointA);
	Planes[index] = D3DXPLANE(Normal.x, Normal.y, Normal.z, offset);

	/*D3DXPlaneFromPoints(&Planes[index], &PointA, &PointB, &PointC);
	D3DXPlaneNormalize(&Planes[index], &Planes[index]);*/
}

Camera::Camera()
{
	_CameraFrame = new Frame();
	_IsPerspective = true;
	D3DXMATRIX view;
	D3DXMatrixIdentity(&view);
	_CameraFrame->SetLocalMatrix(view);
	_Proj = view;
	_Mode = CM_Input;
	_MaxQueueCount = 10;
	_Transition = false;
	_TransitionTime = -1.0f;
	_FOV = _NearPlane = _FarPlane = _AspectRatio = 0.0f;
	_ViewFrustum = NULL;
}

Camera::~Camera()
{
	if(_CameraFrame)
	{
		delete _CameraFrame;
		_CameraFrame = NULL;
	}
	if(_ViewFrustum)
	{
		delete _ViewFrustum;
		_ViewFrustum = NULL;
	}
}

void Camera::CreateViewMatrix(D3DXVECTOR3& Position, D3DXVECTOR3& LookingAt, D3DXVECTOR3& WorldUp)
{
	D3DXMATRIX view;
	D3DXMatrixIdentity(&view);
	D3DXMatrixLookAtLH(&view, &Position, &LookingAt, &WorldUp);
	_CameraFrame->SetLocalMatrix(view);

}

void Camera::MakePerspective(float FOV, float AspectRatio, float NearPlane, float FarPlane)
{
	D3DXMatrixPerspectiveFovLH(&_Proj, FOV, AspectRatio, NearPlane, FarPlane);
	_IsPerspective = true;
	D3DManager::GetDevice()->GetViewport(&_ViewPort);
	_ViewPort.MinZ = 0.1f;
	_ViewPort.MaxZ = 1.0f;
	//D3DManager::GetDevice()->SetViewport(&_ViewPort);

	_FOV = FOV;
	_NearPlane = NearPlane;
	_FarPlane = FarPlane;
	_AspectRatio = AspectRatio;

	if(!_ViewFrustum)
		_ViewFrustum = new Frustum();
}

void Camera::MakeOrthographic(float Width, float Height, float Near, float Far)
{
	D3DXMatrixOrthoLH(&_Proj, Width, Height, Near, Far);
	_IsPerspective = false;
	D3DManager::GetDevice()->GetViewport(&_ViewPort);
	_ViewPort.MinZ = 0.1f;
	_ViewPort.MaxZ = 1.0f;
	//D3DManager::GetDevice()->SetViewport(&_ViewPort);
}

void Camera::UpdateViewport(int X, int Y, int Width, int Height)
{
	_ViewPort.X = X;
	_ViewPort.Y = Y;
	_ViewPort.Width = Width;
	_ViewPort.Height = Height;
}

void Camera::Update(float delta)
{
	_StopWatch.Update();

	switch(_Mode)
	{
	case CM_HardAttach:
		_DoHardAttach(delta);
		BuildFrustum();
		break;
	case CM_HardLerp:
		_DoHardLerp(delta);
		BuildFrustum();
		break;
	case CM_SoftAttach:
		_DoSoftAttach(delta);
		BuildFrustum();
		break;
	case CM_FirstPerson:
		_DoFirstPerson(delta);
		BuildFrustum();
		break;
	case CM_ThirdPerson:
		_DoThirdPerson(delta);
		BuildFrustum();
		break;
	case CM_Input:
		_DoInput(delta);
		BuildFrustum();
		break;
	case CM_DoNothing:
		// IT DOES NOOOOOOOOOOOOOOOOOOOOOTHINGGGGGGGGGGG :D
		BuildFrustum();
		break;

	};
}

void Camera::BuildFrustum()
{
	/*D3DXMATRIX ViewProj;
	D3DXMatrixMultiply(&ViewProj, &GetViewMatrix(), &_Proj);
	_ViewFrustum->Planes[FP_Left].a = ViewProj._14 + ViewProj._11;
	_ViewFrustum->Planes[FP_Left].b = ViewProj._24 + ViewProj._21;
	_ViewFrustum->Planes[FP_Left].c = ViewProj._34 + ViewProj._31;
	_ViewFrustum->Planes[FP_Left].d = ViewProj._44 + ViewProj._41;

	_ViewFrustum->Planes[FP_Right].a = ViewProj._14 - ViewProj._11;
	_ViewFrustum->Planes[FP_Right].b = ViewProj._24 - ViewProj._21;
	_ViewFrustum->Planes[FP_Right].c = ViewProj._34 - ViewProj._31;
	_ViewFrustum->Planes[FP_Right].d = ViewProj._44 - ViewProj._41;

	_ViewFrustum->Planes[FP_Top].a = ViewProj._14 - ViewProj._12;
	_ViewFrustum->Planes[FP_Top].b = ViewProj._24 - ViewProj._22;
	_ViewFrustum->Planes[FP_Top].c = ViewProj._34 - ViewProj._32;
	_ViewFrustum->Planes[FP_Top].d = ViewProj._44 - ViewProj._42;

	_ViewFrustum->Planes[FP_Bottom].a = ViewProj._14 + ViewProj._12;
	_ViewFrustum->Planes[FP_Bottom].b = ViewProj._24 + ViewProj._22;
	_ViewFrustum->Planes[FP_Bottom].c = ViewProj._34 + ViewProj._32;
	_ViewFrustum->Planes[FP_Bottom].d = ViewProj._44 + ViewProj._42;

	_ViewFrustum->Planes[FP_Near].a = ViewProj._14 + ViewProj._13;
	_ViewFrustum->Planes[FP_Near].b = ViewProj._24 + ViewProj._23;
	_ViewFrustum->Planes[FP_Near].c = ViewProj._34 + ViewProj._33;
	_ViewFrustum->Planes[FP_Near].d = ViewProj._44 + ViewProj._43;

	_ViewFrustum->Planes[FP_Far].a = ViewProj._14 - ViewProj._13;
	_ViewFrustum->Planes[FP_Far].b = ViewProj._24 - ViewProj._23;
	_ViewFrustum->Planes[FP_Far].c = ViewProj._34 - ViewProj._33;
	_ViewFrustum->Planes[FP_Far].d = ViewProj._44 - ViewProj._43;

	for(int i = 0; i < FP_MAX; ++i)
	{
	D3DXPlaneNormalize(&_ViewFrustum->Planes[i], &_ViewFrustum->Planes[i]);
	}*/

	const D3DXMATRIX& World = _CameraFrame->GetWorldMatrix();
	D3DXVECTOR3 XAxis(World._11, World._12, World._13);
	D3DXVECTOR3 YAxis(World._21, World._22, World._23);
	D3DXVECTOR3 ZAxis(World._31, World._32, World._33);
	const D3DXVECTOR3& Pos = _CameraFrame->GetPosition();

	D3DXVECTOR3 FarCenter = Pos + ZAxis * _FarPlane;
	D3DXVECTOR3 NearCenter = Pos + ZAxis * _NearPlane;
	float HNear = 2 * tan(_FOV * 0.5f) * _NearPlane;
	float HFar = 2 * tan(_FOV * 0.5f) * _FarPlane;
	float WNear = HNear * _AspectRatio;
	float WFar = HFar * _AspectRatio;

	_ViewFrustum->CornerPoint[FC_FTL] = FarCenter + (YAxis * HFar * 0.5f) - (XAxis * WFar * 0.5f);	// FTL
	_ViewFrustum->CornerPoint[FC_FTR] = FarCenter + (YAxis * HFar * 0.5f) + (XAxis * WFar * 0.5f);	// FTR
	_ViewFrustum->CornerPoint[FC_FBL] = FarCenter - (YAxis * HFar * 0.5f) - (XAxis * WFar * 0.5f);	// FBL
	_ViewFrustum->CornerPoint[FC_FBR] = FarCenter - (YAxis * HFar * 0.5f) + (XAxis * WFar * 0.5f);	// FBR
	_ViewFrustum->CornerPoint[FC_NTL] = NearCenter + (YAxis * HNear * 0.5f) - (XAxis * WNear * 0.5f);	// NTL
	_ViewFrustum->CornerPoint[FC_NTR] = NearCenter + (YAxis * HNear * 0.5f) + (XAxis * WNear * 0.5f);	// NTR
	_ViewFrustum->CornerPoint[FC_NBL] = NearCenter - (YAxis * HNear * 0.5f) - (XAxis * WNear * 0.5f);	// NBL
	_ViewFrustum->CornerPoint[FC_NBR] = NearCenter - (YAxis * HNear * 0.5f) + (XAxis * WNear * 0.5f);	// NBR

	//															D3DXVECTOR3(PointB - PointA), &D3DXVECTOR3(PointA - PointC));
	_ViewFrustum->MakePlane(FP_Left,	_ViewFrustum->CornerPoint[FC_NBL], _ViewFrustum->CornerPoint[FC_FBL], _ViewFrustum->CornerPoint[FC_FTL]);
	_ViewFrustum->MakePlane(FP_Right,	_ViewFrustum->CornerPoint[FC_NTR], _ViewFrustum->CornerPoint[FC_FBR], _ViewFrustum->CornerPoint[FC_NBR]);
	_ViewFrustum->MakePlane(FP_Top,		_ViewFrustum->CornerPoint[FC_NTL], _ViewFrustum->CornerPoint[FC_FTR], _ViewFrustum->CornerPoint[FC_NTR]);
	_ViewFrustum->MakePlane(FP_Bottom,	_ViewFrustum->CornerPoint[FC_NBL], _ViewFrustum->CornerPoint[FC_NBR], _ViewFrustum->CornerPoint[FC_FBL]);
	_ViewFrustum->MakePlane(FP_Far,		_ViewFrustum->CornerPoint[FC_FBL], _ViewFrustum->CornerPoint[FC_FTR], _ViewFrustum->CornerPoint[FC_FTL]);
	_ViewFrustum->MakePlane(FP_Near,	_ViewFrustum->CornerPoint[FC_NBL], _ViewFrustum->CornerPoint[FC_NTR], _ViewFrustum->CornerPoint[FC_NBR]);
}

/// Grabs the camera's world matrix.
const D3DXMATRIX& Camera::GetWorldMatrix()
{
	return _CameraFrame->GetWorldMatrix();
}

/// Grabs the camera's View matrix.
D3DXMATRIX Camera::GetViewMatrix()
{
	if(_IsPerspective)
	{
		//return _CameraFrame->GetWorldMatrix();

		D3DXMATRIX View = _CameraFrame->GetWorldMatrix();
		D3DXVECTOR3 XAxis(View._11, View._12, View._13);
		D3DXVECTOR3 YAxis(View._21, View._22, View._23);
		D3DXVECTOR3 ZAxis(View._31, View._32, View._33);
		D3DXVECTOR3 WAxis(View._41, View._42, View._43);

		View._41 = -D3DXVec3Dot(&XAxis, &WAxis);
		View._42 = -D3DXVec3Dot(&YAxis, &WAxis);
		View._43 = -D3DXVec3Dot(&ZAxis, &WAxis);

		std::swap(View._12, View._21);
		std::swap(View._13, View._31);
		std::swap(View._23, View._32);

		return View;
	}

	D3DXMATRIX view;
	D3DXMatrixIdentity(&view);
	return view;
}

/// Grabs the camera's View matrix.
const D3DXMATRIX& Camera::GetProjectionMatrix()
{
	return _Proj;
}

/// Returns a const* to the camera's frame.
Frame* Camera::GetCameraFrame()
{
	return _CameraFrame;
}

void Camera::ResetCamera()
{
	_CameraFrame->SetPosition(D3DXVECTOR3(0.0f, 0.0f, 0.0f));
	ChangeCameraMode(CM_Input);
}

void Camera::_DoSoftAttach(float delta)
{
	if(true)
	{
		_Mode = CM_Input;
		Informant::Log(LOCATION, "Camera doesn't have a target, switching to a input based camera");
		return;
	}
	const D3DXVECTOR3& MyPos = _CameraFrame->GetPosition();
	D3DXVECTOR3 NewPos;

	/*if(true)
	{
	const D3DXVECTOR3& TargetPos = _Player->GetFrame()->GetPosition();

	if(_Player->GetDirection())
	NewPos = D3DXVECTOR3(TargetPos.x + 12.0f, TargetPos.y + 6.5f, TargetPos.z - 29.5f);
	else
	NewPos = D3DXVECTOR3(TargetPos.x - 12.0f, TargetPos.y + 6.5f, TargetPos.z - 29.5f);
	}
	else
	{
	const D3DXVECTOR3& TargetPos = _Target->GetFrame()->GetPosition();
	NewPos = D3DXVECTOR3(TargetPos.x + 12.0f, TargetPos.y + 6.5f, TargetPos.z - 29.5f);
	}*/

	_QueuedPositons.push(NewPos);
	if(_QueuedPositons.size() > _MaxQueueCount)
	{
		if(!_Transition)
			_LerpToPosition(_QueuedPositons.front(), 0.5f * delta);
		else
			_CameraFrame->SetPosition(_QueuedPositons.front());
		_QueuedPositons.pop();
	}
}

void Camera::_DoHardAttach(float delta)
{
	if(true)
	{
		_Mode = CM_Input;
		Informant::Log(LOCATION, "Camera doesn't have a target, switching to a input based camera");
		return;
	}
	const D3DXVECTOR3& MyPos = _CameraFrame->GetPosition();
	D3DXVECTOR3 NewPos;

	/*if(_Target != NULL)
	{
	const D3DXVECTOR3& TargetPos = _Target->GetFrame()->GetPosition();
	NewPos = D3DXVECTOR3(TargetPos.x, TargetPos.y + 3.5f, TargetPos.z - 40.0f);
	}
	else
	{
	const D3DXVECTOR3& TargetPos = _Player->GetFrame()->GetPosition();
	NewPos = D3DXVECTOR3(TargetPos.x, TargetPos.y + 6.5f, TargetPos.z - 29.5f);
	}*/
	_CameraFrame->SetPosition(NewPos);
}

void Camera::_DoHardLerp(float delta)
{
	if(true)
	{
		_Mode = CM_Input;
		Informant::Log(LOCATION, "Camera doesn't have a target, switching to a input based camera");
		return;
	}
	const D3DXVECTOR3& MyPos = _CameraFrame->GetPosition();
	//const D3DXVECTOR3& TargetPos = _Player->GetFrame()->GetPosition();

	D3DXVECTOR3 NewPos;
	//if(_Player->GetDirection()) // If looking right
	//	NewPos = D3DXVECTOR3(TargetPos.x + (12.0f*.5f), TargetPos.y + 2.5f, TargetPos.z - 29.5f);
	//else
	//	NewPos = D3DXVECTOR3(TargetPos.x - (12.0f*.5f), TargetPos.y + 2.5f, TargetPos.z - 29.5f);

	//if(_FacingRight != _Player->GetDirection())
	//{
	//	_Transition = true;
	//	_TransitionTime = 0.0f;
	//	_FacingRight = _Player->GetDirection();
	//}
	//else if(_TransitionTime > 2.0f)
	//{
	//	_TransitionTime = 0.0f;
	//	_Transition = false;
	//}

	if(_Transition)
	{
		_TransitionTime += delta;
		_LerpToPosition(NewPos, 1.25f * delta);
	}
	else
		_LerpToPosition(NewPos, 2.0f * delta);//_CameraFrame->SetPosition(NewPos);
}


void Camera::_DoInput(float delta)
{
	float modifier = 1.0f;
	/*SWade::CKeyboardInput* Input = WEngine::GetKeyboardInstance();

	if(Input->CheckKey(DIK_LCONTROL) || Input->CheckKey(DIK_RCONTROL))
	modifier = 0.2f;

	if(Input->CheckKey(DIK_Q))
	_CameraFrame->RotateWorldY(-50.0f * modifier * delta);
	if(Input->CheckKey(DIK_E))
	_CameraFrame->RotateWorldY(50.0f * modifier * delta);*/
	if(GetAsyncKeyState('A'))
		_CameraFrame->Translate(-50.0f * modifier * delta, D3DXVECTOR3(1.0f, 0.0f, 0.0f));
	if(GetAsyncKeyState('D'))
		_CameraFrame->Translate(50.0f * modifier * delta, D3DXVECTOR3(1.0f, 0.0f, 0.0f));
	if(GetAsyncKeyState('W'))
		_CameraFrame->Translate(50.0f * modifier * delta, D3DXVECTOR3(0.0f, 0.0f, 1.0f));
	if(GetAsyncKeyState('S'))
		_CameraFrame->Translate(-50.0f * modifier * delta, D3DXVECTOR3(0.0f, 0.0f, 1.0f));
	if(GetAsyncKeyState('R'))
		_CameraFrame->Translate(50.0f * modifier * delta, D3DXVECTOR3(0.0f, 1.0f, 0.0f));
	if(GetAsyncKeyState('F'))
		_CameraFrame->Translate(-50.0f * modifier * delta, D3DXVECTOR3(0.0f, 1.0f, 0.0f));
}

void Camera::_DoFirstPerson(float delta)
{

}

void Camera::_DoThirdPerson(float delta)
{
	if(true)
	{
		_Mode = CM_Input;
		Informant::Log(LOCATION, "Camera doesn't have a player, switching to a input based camera");
		return;
	}

	// Offset vector
	//		D3DXVECTOR3 Offset(0.0f, 0.5f, -0.5f);
	//		D3DXVec3Normalize(&Offset, &Offset);
	//
	//		// Make rotation matrix
	//		D3DXVECTOR3 Scale;
	//		D3DXQUATERNION Rot;
	//		D3DXVECTOR3 Translation;
	////		D3DXMatrixDecompose(&Scale, &Rot, &Translation, &_Player->GetFrame()->GetWorldMatrix());
	//		D3DXMATRIX NewRot;
	//		D3DXMatrixRotationQuaternion(&NewRot, &-Rot);
	//	/*	D3DXMATRIX Rotation;
	//		D3DXMatrixRotationY(&Rotation, Rot.w);*/
	//		D3DXVECTOR4 Transformed;
	//		D3DXVec3Transform(&Transformed, &Offset, &NewRot);
	//
	//		// Calculate the position the camera is looking from
	//		D3DXVECTOR3 CamPos = D3DXVECTOR3(Transformed.x,Transformed.y,Transformed.z) + _Player->GetFrame()->GetPosition();
	//
	//		D3DXMATRIX Look;
	//		D3DXMatrixLookAtLH(&Look, &CamPos, &_Player->GetFrame()->GetPosition(), &D3DXVECTOR3(0.0f,1.0f,0.0f));
	//
	//		D3DXMATRIX Project;
	//		D3DXMatrixPerspectiveFovLH(&Project, 3.14f/4.0f, 1.3333f, 0.1f, 500.0f);
	//
	//		_CameraFrame->SetLocalMatrix(Look);
	//		_Proj = Project;
}

void Camera::_LerpToPosition(D3DXVECTOR3& Pos, float delta)
{
	D3DXVECTOR3 Lerped = Pos, OurPos = _CameraFrame->GetPosition();
	Lerped.x =  OurPos.x + ((Lerped.x - OurPos.x) * delta);
	Lerped.y =  OurPos.y + ((Lerped.y - OurPos.y) * delta * 5.0f);
	//D3DXVec3Lerp(&Lerped, &_CameraFrame->GetPosition(), &Pos, delta);
	_CameraFrame->SetPosition(Lerped);
}